package com.tools.web.work.obs;

import com.fasterxml.jackson.core.type.TypeReference;
import com.obs.services.model.ProgressListener;
import com.obs.services.model.StorageClassEnum;
import com.tools.common.container.MapEnum;
import com.tools.common.container.OwnPairs;
import com.tools.common.exception.InvalidOperationException;
import com.tools.common.io.IOKit;
import com.tools.common.object.json.JsonKit;
import com.tools.common.object.Note;
import com.tools.common.object.ObjKit;
import com.tools.common.object.string.VerifyKit;

import java.io.File;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

/**
 * OBS 对象存储服务的单次操作的构建对象
 * */
@Note("OBS 对象存储服务的单次操作的构建对象")
public final class OBSBuilder {

    @Note("OBS 服务器桶的名称")
    private String bucketName;

    @Note("文件对象的 Key。如果该文件是要放到 OBS 服务器的文件夹下的，那就要将路径 + 对象 Key 拼接存储于此属性中")
    private String objectKey;

    @Note("文件对象的 MIME 类型")
    private String contentType;

    @Note("文件对象的字节输入流")
    private InputStream fileStream;

    @Note("是否开启进度展示，默认关闭")
    private boolean enableProgress = false;

    @Note("进度监听器，若开启了进度展示（enableProgress 为 true），则此属性不能为 null。" +
            "形参的 ProgressStatus 可以获取 AverageSpeed（平均上传速率）、TransferPercentage（上传进度百分比）")
    private ProgressListener progressListener;

    @Note("上传/下载进度的间隔大小，单位是字节，默认是每上传 1MB 的数据就反馈上传进度")
    private long progressInterval = 1024 * 1024;

    @Note("用户上传文件时，可以往 OBS 服务器中添加一些自定义的数据")
    private Map<String, Object> customData;

    @Note("是否开启 MD5 校验文件，默认是关闭的。若上传后的云服务端校验 MD5 失败会上传文件失败，返回 HTTP 400 状态")
    private boolean useMD5 = true;

    @Note("对象的保存类型，为 null 则和桶的类型保持一致。" +
            "STANDARD（标准存储，默认的）。" +
            "WARM（低频访问）" +
            "COLD（归档存储）")
    private StorageClassEnum saveType;

    @Note("大文件分段上传时才会用的上的实例对象")
    private File bigFile;

    @Note("大文件分段上传：该属性设置的是每个分段上传多少字节的数据，默认是 100 MB")
    private long partSize = 100 * 1024 * 1024L;

    @Note("文件下载时指定一个新的文件名")
    private String contentDisposition;

    @Note("文件上传或者下载时，可以指定文件的过期时间，过期后会被 OBS 服务器自动清理。单位【天】")
    private int expires = -1;

    @Note("断点续传过程中如果出现异常，会重新发起续传请求多少次，默认是 3 次。为 0 则不续传。为负数则持续请求")
    private int tryCount = 3;

    @Note("断点上传或下载时可以设置并发。默认是 5")
    private int taskNumber = 5;

    /* **************************************************************************************
     *
     *          构造器
     *
     * **************************************************************************************
     * */

    public OBSBuilder(String bucketName, String objectKey) {
        this.setBucketName(bucketName);
        this.setObjectKey(objectKey);
    }

    public OBSBuilder(String bucketName, String objectKey, String contentType, InputStream fileStream) {
        this.setFileStream(fileStream);
        this.setBucketName(bucketName);
        this.setObjectKey(objectKey);
        this.setContentType(contentType);
    }

    public OBSBuilder(String bucketName, String objectKey, String contentType, File bigFile) {
        this.setBucketName(bucketName);
        this.setObjectKey(objectKey);
        this.setContentType(contentType);
        this.setBigFile(bigFile);
    }

    public static OBSBuilder of(String bucketName, String objectKey) {
        return new OBSBuilder(bucketName, objectKey);
    }

    public static OBSBuilder of(String bucketName, String objectKey, String contentType, InputStream fileStream) {
        return new OBSBuilder(bucketName, objectKey, contentType, fileStream);
    }

    public static OBSBuilder of(String bucketName, String objectKey, String contentType, File bigFile) {
        return new OBSBuilder(bucketName, objectKey, contentType, bigFile);
    }

    /* **************************************************************************************
     *
     *          Getter / Setter
     *
     * **************************************************************************************
     * */

    public String getBucketName() {
        return bucketName;
    }

    public void setBucketName(String bucketName) {
        if(bucketName == null || bucketName.isEmpty()) throw new NullPointerException("桶的名称不能为空");
        this.bucketName = bucketName;
    }

    public String getObjectKey() {
        return objectKey;
    }

    public void setObjectKey(String objectKey) {
        if(objectKey == null || objectKey.isEmpty()) throw new NullPointerException("文件对象的 Key 不能为空");
        this.objectKey = objectKey;
    }

    public String getContentType() {
        return contentType;
    }

    public void setContentType(String contentType) {
        if(contentType == null || contentType.isEmpty()) throw new NullPointerException("文件对象的 MIME 类型不能为空");
        this.contentType = contentType;
    }

    public InputStream getFileStream() {
        return fileStream;
    }

    public void setFileStream(InputStream fileStream) {
        if(fileStream == null) throw new NullPointerException("要上传的文件对象的字节输入流不能为 null");
        this.fileStream = fileStream;
    }

    public void setFileStream(File file) {
        if(file == null) throw new NullPointerException("要上传的文件对象不能为 null");
        this.fileStream = IOKit.fileInputStream(file);
    }

    public void setFileStream(String filePath) {
        if(filePath == null || filePath.isEmpty()) throw new NullPointerException("要上传的文件的路径不能为 null");
        this.fileStream = IOKit.fileInputStream(filePath);
    }

    public boolean isEnableProgress() {
        return enableProgress;
    }

    public void setEnableProgress(boolean enableProgress) {
        this.enableProgress = enableProgress;
    }

    public ProgressListener getProgressListener() {
        if(enableProgress && progressListener == null) {
            throw new InvalidOperationException("开启了进度展示，则进度监听器 ProgressListener 不能为 null");
        }
        return progressListener;
    }

    public void setProgressListener(ProgressListener progressListener) {
        this.progressListener = progressListener;
    }

    public long getProgressInterval() {
        return progressInterval;
    }

    public void setProgressInterval(long progressInterval) {
        this.progressInterval = (progressInterval <= 0) ? this.progressInterval : progressInterval;
    }

    public boolean isUseMD5() {
        return useMD5;
    }

    public void setUseMD5(boolean useMD5) {
        this.useMD5 = useMD5;
    }

    public StorageClassEnum getSaveType() {
        return saveType;
    }

    public void setSaveType(StorageClassEnum saveType) {
        this.saveType = saveType;
    }

    public File getBigFile() {
        return bigFile;
    }

    public void setBigFile(File bigFile) {
        if(bigFile == null) throw new NullPointerException("入参的大文件不能为 null");
        this.bigFile = bigFile;
    }

    public void setBigFile(String bigFilePath) {
        if(bigFilePath == null || bigFilePath.isEmpty()) throw new NullPointerException("入参的大文件路径不能为空");
        this.bigFile = new File(bigFilePath);
    }

    public long getPartSize() {
        return partSize;
    }

    public void setPartSize(long partSize) {
        this.partSize = (partSize <= 0) ? this.partSize : partSize;
    }

    public Map<String, Object> getCustomData() {
        return customData;
    }

    public void setCustomData(Map customData) {
        if(customData == null) {
            this.customData = null;
            return;
        }
        if(customData.isEmpty()) {
            this.customData = new HashMap<>();
            return;
        }
        this.customData = new HashMap<>(MapEnum.calcInitSize(customData.size()));
        private_putData(customData);
    }

    public void setCustomData(OwnPairs customData) {
        if(customData == null) {
            this.customData = null;
        } else if(customData.isEmpty()) {
            this.customData = new HashMap<>();
        } else {
            this.customData = new HashMap<>(MapEnum.calcInitSize(customData.size()));
            private_putData(customData);
        }
    }

    public void setCustomData(String jsonObject) {
        if(VerifyKit.isJsonObject(jsonObject)) {
            this.customData = JsonKit.toEntity(jsonObject, new TypeReference<HashMap<String, Object>>() {});
            return;
        }
        this.customData = null;
    }

    public void setCustomData(Object javaBean) {
        if(javaBean == null) {
            this.customData = null;
            return;
        }
        this.customData = ObjKit.allFieldValue(javaBean);
    }

    public String getContentDisposition() {
        return contentDisposition;
    }

    public void setContentDisposition(String contentDisposition) {
        this.contentDisposition = contentDisposition;
    }

    public int getExpires() {
        return expires;
    }

    public void setExpires(int expires) {
        this.expires = expires;
    }

    public int getTryCount() {
        return tryCount;
    }

    public void setTryCount(int tryCount) {
        this.tryCount = tryCount;
    }

    public int getTaskNumber() {
        return taskNumber;
    }

    public void setTaskNumber(int taskNumber) {
        this.taskNumber = Math.max(taskNumber, 0);
    }

    /* **************************************************************************************
     *
     *          链式调用
     *
     * **************************************************************************************
     * */

    public OBSBuilder bucketName(String bucketName) {
        this.setBucketName(bucketName);
        return this;
    }

    public OBSBuilder objectKey(String objectKey) {
        this.setObjectKey(objectKey);
        return this;
    }

    public OBSBuilder contentType(String contentType) {
        this.setContentType(contentType);
        return this;
    }

    public OBSBuilder fileStream(InputStream fileStream) {
        this.setFileStream(fileStream);
        return this;
    }

    public OBSBuilder fileStream(File file) {
        this.setFileStream(file);
        return this;
    }

    public OBSBuilder fileStream(String filePath) {
        this.setFileStream(filePath);
        return this;
    }

    public OBSBuilder enableProgress(boolean enableProgress) {
        this.setEnableProgress(enableProgress);
        return this;
    }

    public OBSBuilder progressListener(ProgressListener progressListener) {
        this.setProgressListener(progressListener);
        return this;
    }

    public OBSBuilder progressInterval(long progressInterval) {
        this.setProgressInterval(progressInterval);
        return this;
    }

    public OBSBuilder useMD5(boolean useMD5) {
        this.setUseMD5(useMD5);
        return this;
    }

    public OBSBuilder saveType(StorageClassEnum saveType) {
        this.setSaveType(saveType);
        return this;
    }

    public OBSBuilder bigFile(File bigFile) {
        this.setBigFile(bigFile);
        return this;
    }

    public OBSBuilder bigFile(String bigFilePath) {
        this.setBigFile(bigFilePath);
        return this;
    }

    public OBSBuilder partSize(long partSize) {
        this.setPartSize(partSize);
        return this;
    }

    public OBSBuilder customData(Map customData) {
        this.setCustomData(customData);
        return this;
    }

    public OBSBuilder customData(OwnPairs customData) {
        this.setCustomData(customData);
        return this;
    }

    public OBSBuilder customData(String jsonObject) {
        this.setCustomData(jsonObject);
        return this;
    }

    public OBSBuilder customData(Object javaBean) {
        this.setCustomData(javaBean);
        return this;
    }

    public OBSBuilder contentDisposition(String contentDisposition) {
        this.setContentDisposition(contentDisposition);
        return this;
    }

    public OBSBuilder expires(int expires) {
        this.setExpires(expires);
        return this;
    }

    public OBSBuilder tryCount(int tryCount) {
        this.setTryCount(tryCount);
        return this;
    }

    public OBSBuilder taskNumber(int taskNumber) {
        this.setTaskNumber(taskNumber);
        return this;
    }

    /* **************************************************************************************
     *
     *          自定义方法
     *
     * **************************************************************************************
     * */

    public OBSBuilder addData(String name, Object value) {
        private_initCustomData(2);
        this.customData.put(name, value);
        return this;
    }

    public OBSBuilder addData(Map customData) {
        if(customData == null || customData.isEmpty()) return this;
        private_initCustomData(MapEnum.calcInitSize(customData.size()));
        return private_putData(customData);
    }

    public OBSBuilder addData(OwnPairs customData) {
        if(customData == null || customData.isEmpty()) return this;
        private_initCustomData(MapEnum.calcInitSize(customData.size()));
        return private_putData(customData);
    }

    public OBSBuilder addData(String jsonObject) {
        if(VerifyKit.isJsonObject(jsonObject)) {
            HashMap<String, Object> map = JsonKit.toEntity(jsonObject, new TypeReference<HashMap<String, Object>>() {});
            if(map.isEmpty()) return this;
            private_initCustomData(map.size());
            this.customData.putAll(map);
        }
        return this;
    }

    public OBSBuilder addData(Object customData) {
        if(customData == null) return this;
        Map<String, Object> map = ObjKit.allFieldValue(customData);
        if(map.isEmpty()) return this;
        private_initCustomData(MapEnum.calcInitSize(map.size()));
        this.customData.putAll(map);
        return this;
    }

    public OBSBuilder removeData(String name) {
        if(customData == null || customData.isEmpty()) return this;
        this.customData.remove(name);
        return this;
    }

    public OBSBuilder removeData(String... names) {
        if(customData == null || customData.isEmpty() || names == null || names.length == 0) return this;
        for (String name : names) this.customData.remove(name);
        return this;
    }

    public OBSBuilder removeData(Iterable<String> names) {
        if(customData == null || customData.isEmpty() || names == null) return this;
        for (String name : names) this.customData.remove(name);
        return this;
    }

    public boolean containsData(String name) {
        if(customData == null || customData.isEmpty()) return false;
        return this.customData.containsKey(name);
    }

    public Object getData(String name) {
        if(customData == null || customData.isEmpty()) return null;
        return this.customData.get(name);
    }

    /* **************************************************************************************
     *
     *          私有逻辑
     *
     * **************************************************************************************
     * */

    @Note("初始化自定义属性数据 Map 集合")
    private void private_initCustomData(int initSize) {
        if(customData == null) this.customData = new HashMap<>(MapEnum.calcInitSize(initSize));
    }

    @Note("将参数 Map 的键值对放入自定义属性数据 Map 集合里")
    private OBSBuilder private_putData(Map customData) {
        for (Object o : customData.entrySet()) {
            Map.Entry e = (Map.Entry) o;
            this.customData.put(ObjKit.asStringValue(e.getValue(), null), e.getValue());
        }
        return this;
    }

    @Note("将参数自定义容器的键值对放入自定义属性数据 Map 集合里")
    private OBSBuilder private_putData(OwnPairs customData) {
        for (Object o : customData.entrySet()) {
            Map.Entry e = (Map.Entry) o;
            this.customData.put(ObjKit.asStringValue(e.getValue(), null), e.getValue());
        }
        return this;
    }
}
